// eTestbed.cpp : Defines the entry point for the console application.
//

#define USE_GPROF
#undef  STRICT

#include "../Epsilon/epsilon.h" // eEngine* epsilon
#include "../Epsilon/Epsilon3D/OpenGL.h"

#include "states.h"
#include "console.h"
#include "command.h"
#include "window.h"

#include "cli_parse/module.h"


namespace Underlight
{
  class File;
}


eTestbedStates* states = NULL;
#define g_Window states->window


/* HACK TO ALLOW GPROF... */
bool         invoke_exit = false;

eTestbedStates* init_testbed (void); /* Init states / cmd processor.    */
void            dump_devices (void); /* Dump display devices to stdout. */

int _tmain (int argc, TCHAR* argv [])
{
  states = init_testbed ();

///  Epsilon::Main::Initialize ();

  dump_devices ();


  /* User-Defined Data (parsed from the command-line), passed to
       the LoadResources (...) method defined by the run-time Module. */
  void* user_data = NULL;


  /* What Module to Load... */
  if (false) {
    //win1.Create (_T ("Epsilon Testbed  [Simple Test Scene]"), 0, 0);
    
///    if (! input)
///      input = new eTBInput (win1.win);
  } else {
    /* Load cli_parse. */
    cli_parse::Init ();

    /* TODO: Module::ParseCommandLine (...) */
    int* level_num = new int;

    /* Parse the Command Line. */
    if (argc > 1 && argc < 3) {
      *level_num = atoi (argv [1]);
    } else {
      *level_num = -1; /* -1 will prompt the user for the level number... */
    }

    user_data = level_num;
  }

  g_Window->win->Resize (1670, 925);
  g_Window->win->Move   (5,     40);

  g_Window->OnResize ((eWindowRect&)g_Window->win->getRect ());

//  win2.win->ResizeClient (128, 128);
//  win2.win->MoveClient   (32, 64);

  states->gui_console = new eTB_Console_Render ();

  /* If the level number was not already specified in the config file,
       then prompt the user for a level at run-time. */
  if ( (cli_parse::states->cl_level_num >= 1 &&
        cli_parse::states->cl_level_num <= 45) ||
       states->LoadResources (user_data) )
  {
    while (! invoke_exit) {
//      gui_console->printf ("Frame: %d\n", i++);
      /* Update the contents of the console. */
      states->console->Display ();

      epsilon.Think ();

      g_Window->DrawFrame ();
//      win2.DrawTestScene ();

//// XXX: Port Me
#ifdef WIN32
      eHWND hwnd (g_Window->win->getNativeWindowHandle ());
      if (! ::IsWindow (hwnd))
        break;
#endif
    }
  }

  states->Terminate (0);

///  Epsilon::Main::Terminate ();

  return 0;
}



eTestbedStates*
init_testbed (void)
{
  eTestbedStates* states = new eTestbedStates ();

  /* Initialize the console ASAP, so that module initialization
       can print startup and debug info. */
  states->command = new eTB_CommandProcessor ();
  states->console = new eTB_Console_StdOut   ();

  eTB_CommandProcessor* command = states->command;

  /* We need to have the window object created ASAP, so we can
       use r_window_res_..., even before the window is actually
         created. */
  states->window = new eTB_RenderWindow ();

  /* eTestbed Global States */
  command->AddVariable ("cl_fly",
    new eTB_VarStub <bool> (&states->cl_fly));

  command->AddVariable ("cl_verbose" ,
    new eTB_VarStub <bool> (&states->cl_verbose));

  command->AddVariable ("r_stat_overlay",
    new eTB_VarStub <bool> (&states->r_stat_overlay));

  command->AddVariable ("r_batch_print",
    new eTB_VarStub <bool> (&states->r_batch_print));

  memset (&states->key_states, sizeof (bool) * 256, 0);

  command->AddVariable ("r_window_res_x",
    new eTB_VarStub <int>  (&states->r_window_res_x, states->window));
  command->AddVariable ("r_window_res_y",
    new eTB_VarStub <int>  (&states->r_window_res_y, states->window));

  return states;
}


#include "../Epsilon/Epsilon3D/device.h"
#include "../Epsilon/Epsilon3D/driver.h"

#include "../Epsilon/STL.h"

void
dump_devices (void)
{
  e3dDisplayDevices devices = e3dDriver::GetDevices ();

  e3dDisplayDevices::iterator iter = devices.begin ();
  e3dDisplayDevices::iterator end  = devices.end   ();

  while (eSTL::IterValid (iter, end)) {
    ((e3dDisplayDevice*)&(*iter))->print ();
    iter++;
  }
}

//
// Default Event Handler Stubs
//
void CALLBACK HandleCamera (void)
{
}

#include "../Epsilon/timer.h"
bool CALLBACK TimedUpdates (void)
{
////  static eFrameTimer timer;

////  timer.Begin ();
  return true;
}


#ifdef USE_TEST_SCENE

void
BeginFrame_TestScene (void)
{
  static float rquad = 0.0f;
  static float rtri  = 0.0f;

  static float rmag = 1.0f;
}

void
EndFrame_TestScene (void)
{
  /*
  rtri  += (0.2f  * timer.FrameInterval () * 1000);
  rquad -= (0.15f * timer.FrameInterval () * 1000);

  rmag = -rmag;
  */
}

#include "../Epsilon/Epsilon3D/vcache.h"
e3dVertexCache* vcache1 = NULL;
e3dVertexCache* vcache2 = NULL;


void TestScene_DebugVertexCacheMemory (void)
{
  // Use this to test for memory leaks
  if (vcache1 != NULL) {
    delete vcache1;
    vcache1 = NULL;
  }
  if (vcache2 != NULL) {
    delete vcache2;
    vcache2 = NULL;
  }
}

#include "../Epsilon/Epsilon3D/immediate.h"
bool CALLBACK LoadResources_TestScene (void)
{
  /*
  void DebugVertexCacheMemory (void)

  if (vcache1 == NULL) {
    vcache1 = new e3dVertexCache ();

    e3dBegin (vcache1);

      e3dColor3f(  1.0f,0.0f,0.0f);            // Red
      e3dVertex3f( 0.0f, 1.0f, 0.0f);          // Top Of Triangle (Front)
      e3dColor3f  (0.0f,1.0f,0.0f);            // Green
      e3dVertex3f(-1.0f,-1.0f, 1.0f);          // Left Of Triangle (Front)
      e3dColor3f  (0.0f,0.0f,1.0f);            // Blue
      e3dVertex3f( 1.0f,-1.0f, 1.0f);          // Right Of Triangle (Front)
      e3dColor3f  (1.0f,0.0f,0.0f);            // Red
      e3dVertex3f( 0.0f, 1.0f, 0.0f);          // Top Of Triangle (Right)
      e3dColor3f  (0.0f,0.0f,1.0f);            // Blue
      e3dVertex3f( 1.0f,-1.0f, 1.0f);          // Left Of Triangle (Right)
      e3dColor3f  (0.0f,1.0f,0.0f);            // Green
      e3dVertex3f(1.0f,-1.0f, -1.0f);          // Right Of Triangle (Right)
      e3dColor3f  (1.0f,0.0f,0.0f);            // Red
      e3dVertex3f( 0.0f, 1.0f, 0.0f);          // Top Of Triangle (Back)
      e3dColor3f  (0.0f,1.0f,0.0f);            // Green
      e3dVertex3f(1.0f,-1.0f, -1.0f);          // Left Of Triangle (Back)
      e3dColor3f  (0.0f,0.0f,1.0f);            // Blue
      e3dVertex3f(-1.0f,-1.0f, -1.0f);         // Right Of Triangle (Back)
      e3dColor3f  (1.0f,0.0f,0.0f);            // Red
      e3dVertex3f( 0.0f, 1.0f, 0.0f);          // Top Of Triangle (Left)
      e3dColor3f  (0.0f,0.0f,1.0f);            // Blue
      e3dVertex3f(-1.0f,-1.0f,-1.0f);          // Left Of Triangle (Left)
      e3dColor3f  (0.0f,1.0f,0.0f);            // Green
      e3dVertex3f(-1.0f,-1.0f, 1.0f);          // Right Of Triangle (Left)

    e3dEnd ();
  }

  if (vcache2 == NULL) {
    vcache2 = new e3dVertexCache ();

    e3dBegin (vcache2);

      e3dColor3f  (0.0f,1.0f,0.0f);            // Set The Color To Green
      e3dVertex3f( 1.0f, 1.0f,-1.0f);          // Top Right Of The Quad (Top)
      e3dVertex3f(-1.0f, 1.0f,-1.0f);          // Top Left Of The Quad (Top)
      e3dVertex3f(-1.0f, 1.0f, 1.0f);          // Bottom Left Of The Quad (Top)
      e3dVertex3f( 1.0f, 1.0f, 1.0f);          // Bottom Right Of The Quad (Top)
      e3dColor3f  (1.0f,0.5f,0.0f);            // Set The Color To Orange
      e3dVertex3f( 1.0f,-1.0f, 1.0f);          // Top Right Of The Quad (Bottom)
      e3dVertex3f(-1.0f,-1.0f, 1.0f);          // Top Left Of The Quad (Bottom)
      e3dVertex3f(-1.0f,-1.0f,-1.0f);          // Bottom Left Of The Quad (Bottom)
      e3dVertex3f( 1.0f,-1.0f,-1.0f);          // Bottom Right Of The Quad (Bottom)
      e3dColor3f  (1.0f,0.0f,0.0f);            // Set The Color To Red
      e3dVertex3f( 1.0f, 1.0f, 1.0f);          // Top Right Of The Quad (Front)
      e3dVertex3f(-1.0f, 1.0f, 1.0f);          // Top Left Of The Quad (Front)
      e3dVertex3f(-1.0f,-1.0f, 1.0f);          // Bottom Left Of The Quad (Front)
      e3dVertex3f( 1.0f,-1.0f, 1.0f);          // Bottom Right Of The Quad (Front)
      e3dColor3f  (1.0f,1.0f,0.0f);            // Set The Color To Yellow
      e3dVertex3f( 1.0f,-1.0f,-1.0f);          // Top Right Of The Quad (Back)
      e3dVertex3f(-1.0f,-1.0f,-1.0f);          // Top Left Of The Quad (Back)
      e3dVertex3f(-1.0f, 1.0f,-1.0f);          // Bottom Left Of The Quad (Back)
      e3dVertex3f( 1.0f, 1.0f,-1.0f);          // Bottom Right Of The Quad (Back)
      e3dColor3f  (0.0f,0.0f,1.0f);            // Set The Color To Blue
      e3dVertex3f(-1.0f, 1.0f, 1.0f);          // Top Right Of The Quad (Left)
      e3dVertex3f(-1.0f, 1.0f,-1.0f);          // Top Left Of The Quad (Left)
      e3dVertex3f(-1.0f,-1.0f,-1.0f);          // Bottom Left Of The Quad (Left)
      e3dVertex3f(-1.0f,-1.0f, 1.0f);          // Bottom Right Of The Quad (Left)
      e3dColor3f  (1.0f,0.0f,1.0f);            // Set The Color To Violet
      e3dVertex3f( 1.0f, 1.0f,-1.0f);          // Top Right Of The Quad (Right)
      e3dVertex3f( 1.0f, 1.0f, 1.0f);          // Top Left Of The Quad (Right)
      e3dVertex3f( 1.0f,-1.0f, 1.0f);          // Bottom Left Of The Quad (Right)
      e3dVertex3f( 1.0f,-1.0f,-1.0f);          // Bottom Right Of The Quad (Right)

    e3dEnd ();
  }
  */

  return true;
}

bool
DrawScene_TestScene (void)
{
  /*
    extern void e3dLoadIdentity (void);
    extern void e3dTranslatef   (float x, float y, float z);
    extern void e3dRotatef      (float mag, float x, float y, float z);

    e3dLoadIdentity  ();
    e3dTranslatef    (-1.5f, 0.0f, -6.0f);
    e3dRotatef       (rtri,  0.0f,  rmag, 0.0f);

    vcache1->Bind    ();
    vcache1->Enable  ();
      vcache1->Draw  (e3dRenderStates::Triangles, vcache1->GetSize ());
    vcache1->Disable ();

    e3dLoadIdentity  ();
    e3dTranslatef    (1.5f, 0.0f, -7.0f);
    e3dRotatef       (rquad, rmag, rmag, rmag);

    vcache2->Bind    ();
    vcache2->Enable  ();
      vcache2->Draw  (e3dRenderStates::Quads, vcache2->GetSize ());
    vcache2->Disable ();
  */

    return true;
}

void DrawTestScene (e3dRenderContext* rc, e3dViewport* viewport)
{
  /*
  static eFrameTimer timer;

  static float rquad = 0.0f;
  static float rtri  = 0.0f;

  static float rmag = 1.0f;

  timer.Begin ();

  rc->BeginFrame ();
  {
    extern void SetupView (e3dViewport* viewport);
    SetupView (viewport);

    // Add Batch Transformations Here
    //
    ///e3dStart  (...);
    /// ...
    ///e3dFinish (...);

    //rc->QueueBatch  (...);
    // ...
    //
    //rc->SortBatches (...);
    //rc->DrawBatches (...);

    e3dLoadIdentity ();

    if (! DrawScene_cli_parse ()) {
      DrawScene_cli_parse ();
    }
  }
  rc->EndFrame ();

  timer.End ();

  rtri  += (0.2f  * timer.FrameInterval () * 1000);
  rquad -= (0.15f * timer.FrameInterval () * 1000);

  rmag = -rmag;
  */
}
#endif USE_TEST_SCENE


void
draw_dark_overlay (float brightness)
{
  struct OverlayVertex
  {
    int           pos   [4];
    unsigned char color [4];
  };

  OverlayVertex aOverlayVertices [4];

  OverlayVertex* pVert = aOverlayVertices;

  float color = brightness;

  for (int i = 0; i < 4; i++) {
            aOverlayVertices [i].pos [2] = 0;
            aOverlayVertices [i].pos [3] = 1; // w = 1.0f
    (DWORD&)aOverlayVertices [i].color   = D3DCOLOR_COLORVALUE (0.0f, 0.0f, 0.0f, color);
  }

  pVert->pos [0] = 0;
  pVert->pos [1] = 0;

  pVert++;

  pVert->pos [0] = 0;
  pVert->pos [1] = g_Window->viewport.Height ();

  pVert++;

  pVert->pos [0] = g_Window->viewport.Width ();
  pVert->pos [1] = 0;

  pVert++;

  pVert->pos [0] = g_Window->viewport.Width  ();
  pVert->pos [1] = g_Window->viewport.Height ();

/// TODO: Port Me
#ifdef HAS_D3D
  //
  // Direct3D Specific
  //
  if (! e3dDirect3D::IsDevicePresent ())
    return;

  LPDIRECT3DDEVICE9 dev = e3dDirect3D::GetDev ();

  dev->SetFVF               ( D3DFVF_XYZRHW  |
                              D3DFVF_DIFFUSE );

  dev->SetRenderState       (D3DRS_ALPHABLENDENABLE, true);
  dev->SetRenderState       (D3DRS_SRCBLEND,         D3DBLEND_ZERO);
  dev->SetRenderState       (D3DRS_DESTBLEND,        D3DBLEND_SRCALPHA);

  dev->SetTexture           (0, NULL);
  dev->DrawPrimitiveUP      (D3DPT_TRIANGLESTRIP, 2, aOverlayVertices, sizeof (OverlayVertex));

  dev->SetRenderState       (D3DRS_ALPHABLENDENABLE, false);
#else
  glPushAttrib       (GL_ALL_ATTRIB_BITS);
  glPushClientAttrib (GL_CLIENT_ALL_ATTRIB_BITS);

  glMatrixMode   (GL_MODELVIEW);
  glPushMatrix   ();
  glLoadIdentity ();

  glMatrixMode   (GL_PROJECTION);
  glPushMatrix   ();
  glLoadIdentity ();

  glOrtho (0, g_Window->viewport.Width (), 0, g_Window->viewport.Height (), -1.0f, 1.0f);

  glDisable (GL_DEPTH_TEST);

  glFrontFace (GL_CW);

//  cli_parse::states->render_context->SetCullFaces (e3dRenderStates::False);
//  cli_parse::states->render_context->SetDepthTest (e3dRenderStates::False);

  glDisable (GL_COLOR_MATERIAL);
  glDisable (GL_TEXTURE_2D);

  glDisableClientState (GL_COLOR_ARRAY);

  glEnable (GL_BLEND);

  /// Originally: GL_ZERO, GL_SRC_ALPHA   - Changed to "fix" texture coord debug overlay
  glBlendFunc (GL_ZERO, GL_ONE_MINUS_SRC_ALPHA);

  glBegin (GL_TRIANGLE_STRIP);

  glColor4f (0.0f, 0.0f, 0.0f, 1.0f - brightness);

  glVertex2i (aOverlayVertices [0].pos [0],
              aOverlayVertices [0].pos [1]);

  glVertex2i (aOverlayVertices [1].pos [0],
              aOverlayVertices [1].pos [1]);

  glVertex2i (aOverlayVertices [2].pos [0],
              aOverlayVertices [2].pos [1]);

  glVertex2i (aOverlayVertices [3].pos [0],
              aOverlayVertices [3].pos [1]);

  glEnd ();

//  glDisable (GL_BLEND);

  glMatrixMode (GL_PROJECTION);
  glPopMatrix  ();

  glMatrixMode (GL_MODELVIEW);
  glPopMatrix  ();

  glPopClientAttrib ();
  glPopAttrib       ();

  //cli_parse::states->render_context->SetCullFaces (e3dRenderStates::True);
  //cli_parse::states->render_context->SetDepthTest (e3dRenderStates::True);
#endif
}